/*
 *   Copyright 2012, Thomas Kerber
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package milk.skimmed;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;

import milk.jpatch.JPatch;

/**
 * The main class of skimmed milk. Manages misc. methods critical for
 * making use of skimmed milk.
 * @author Thomas Kerber
 * @version 1.0.0
 */
public class Skimmed{
    
    /**
     * The minecraft root directory.
     */
    public static final File MINECRAFT_ROOT;
    
    static{
        // Find minecraft root. Modeled after minecrafts
        // Util.getWorkingDirectory
        String osName = System.getProperty("os.name").toLowerCase();
        if(osName.contains("win")){
            String applicationData = System.getenv("APPDATA");
            if(applicationData != null)
                MINECRAFT_ROOT = new File(applicationData, ".minecraft");
            else
                MINECRAFT_ROOT = new File(System.getProperty("user.home", "."),
                        ".minecraft"); 
        }
        else if(osName.contains("mac")){
            MINECRAFT_ROOT =
                    new File(new File(new File(
                            System.getProperty("user.home","."),
                            "Library"),
                            "Application Support"),
                            "minecraft");
        }
        else if(osName.contains("solaris") || osName.contains("sunos") ||
                osName.contains("linux") || osName.contains("unix")){
            MINECRAFT_ROOT = new File(System.getProperty("user.home","."),
                    ".minecraft");
        }
        else{
            MINECRAFT_ROOT = new File(System.getProperty("user.home","."),
                    "minecraft");
        }
    }
    
    /**
     * Milks root directory.
     */
    public static final File MILK_ROOT = new File(MINECRAFT_ROOT, "milk");
    
    /**
     * The opt root. This is the directory from which jarps are loaded.
     */
    public static final File OPT_ROOT = new File(MILK_ROOT, "opt");
    
    /**
     * The original minecraft.jar (milk maintains a copy).
     */
    public static final File MINECRAFT_ORIG = new File(MILK_ROOT, "orig.jar");
    
    /**
     * The active configuration.
     */
    public static Configuration CONFIG;
    
    static{
        
        try{
            ensureVersionCorrectness();
        }
        catch(IOException e){
            Util.logger.log(Level.SEVERE, "", e);
            Callback.issueError("IO error occurred during version " +
                    "checks. See log for details.");
        }
        CONFIG = ConfigFallbackHistory.INSTANCE.get();
        // Find new Jarps.
        outer: for(File f : OPT_ROOT.listFiles()){
            if(f.getName().toLowerCase().endsWith(".jarp")){
                for(Jarp j : CONFIG.getJarpList()){
                    if(j.getName().equals(f.getName()))
                        continue outer;
                }
                try{
                    CONFIG.addJarp(new Jarp(f));
                }
                catch(IOException e){
                    Util.logger.log(Level.WARNING, "", e);
                    Callback.issueWarning(
                            "Failed to load Jarp due to an IO read error.");
                }
            }
        }
        // Find removed Jarps.
        outer: for(Jarp j : CONFIG.getJarpList()){
            for(File f : OPT_ROOT.listFiles()){
                if(f.getName().toLowerCase().endsWith(".jarp")){
                    if(j.getName().equals(f.getName()))
                        continue outer;
                }
            }
            CONFIG.removeJarp(j);
        }
    }
    
    /**
     * Generates the basic file structure for milk.
     * @throws IOException
     */
    private static void genStructure() throws IOException{
        File versionInfo = new File(MILK_ROOT, "mc_ver");
        File mcJar = new File(new File(MINECRAFT_ROOT, "bin"),
                "minecraft.jar");
        OPT_ROOT.mkdirs();
        OutputStream out = new FileOutputStream(versionInfo);
        out.write(Util.digest(mcJar));
        out.close();
        Util.copy(mcJar, MINECRAFT_ORIG);
    }
    
    /**
     * Ensures that milk is set for the correct version of minecraft.
     * @throws IOException
     */
    public static void ensureVersionCorrectness() throws IOException{
        // mc_ver saves the *last known* hash of minecraft.jar.
        File versionInfo = new File(MILK_ROOT, "mc_ver");
        File mcJar = new File(new File(MINECRAFT_ROOT, "bin"),
                "minecraft.jar");
        if(!versionInfo.exists()){
            genStructure();
        }
        else{
            byte[] v = new byte[64];
            InputStream in = new FileInputStream(versionInfo);
            in.read(v);
            in.close();
            byte[] md = Util.digest(mcJar);
            if(!Arrays.equals(v, md)){
                // Move milk to tmp dir, recreate milk, move any previous
                // old versions to new milk and then move new milk back.
                File tmpD = milk.jpatch.Util.getTempDir();
                MILK_ROOT.renameTo(tmpD);
                String hexmd = Util.byteArrayToHex(md);
                boolean foundOldMatch = false;
                for(File f : tmpD.listFiles()){
                    if(f.getName().equals(hexmd)){
                        Callback.notifyUser(
                                "Your minecraft version has changed to one " +
                                "you had before. We are loading your old " +
                                "mods...");
                        f.renameTo(MILK_ROOT);
                        foundOldMatch = true;
                        break;
                    }
                }
                if(!foundOldMatch){
                    Callback.notifyUser("You seem to have updated " +
                            "your minecraft version! We are clearing out " +
                            "your old mods...");
                    genStructure();
                }
                for(File f : tmpD.listFiles()){
                    if(f.isDirectory() && f.getName().length() == 64){
                        f.renameTo(new File(MILK_ROOT, f.getName()));
                    }
                }
                
                // Store the original digest in mc_ver.
                byte[] origDigest = Util.digest(new File(tmpD, "orig.jar"));
                OutputStream out = new FileOutputStream(
                        new File(tmpD, "mc_ver"));
                out.write(origDigest);
                out.close();
                tmpD.renameTo(new File(MILK_ROOT,
                        Util.byteArrayToHex(origDigest)));
            }
        }
    }
    
    /**
     * 
     * @return Whether or not the modded version can be built.
     */
    public static boolean canBuild(){
        return CONFIG.allReqsMet();
    }
    
    /**
     * Tests minecraft.
     * @throws IOException
     */
    public static void test() throws IOException{
        ProcessBuilder pb = new ProcessBuilder(
                "java",
                "-cp",
                "minecraft.jar:lwjgl.jar:lwjgl_util.jar:jinput.jar",
                "-Djava.library.path=natives",
                "net.minecraft.client.Minecraft");
        pb.redirectErrorStream(true);
        pb.directory(new File(MINECRAFT_ROOT, "bin"));
        Process p = pb.start();
        BufferedReader input = new BufferedReader(
                new InputStreamReader(p.getInputStream()));
        String line;
        while((line = input.readLine()) != null){
            Util.logger.log(Level.INFO, line);
        }
        try{
            p.waitFor();
        }
        catch(InterruptedException e){}
        Util.logger.log(Level.INFO, "Exit status " + p.exitValue());
    }
    
    /**
     * Builds minecraft.
     * 
     * This applies all active patches, copies over the minecraft jar, creates
     * a fallback history and runs it. A sanity test must first be passed.
     * @param test Whether or not to run a test after a successful build.
     * @throws IOException
     * @throws InsaneException
     */
    public static void build(boolean test) throws IOException, InsaneException{
        List<Jarp> jarpList = CONFIG.getLoadList();
        File f = genSelection(jarpList.toArray(new Jarp[jarpList.size()]));
        
        SanityChecker.check(f);
        
        ConfigFallbackHistory.INSTANCE.add();
        // TODO: delete before renames. (Check others)
        File mc = new File(new File(MINECRAFT_ROOT, "bin"), "minecraft.jar");
        mc.delete();
        f.renameTo(mc);
        // Update the version file.
        File versionInfo = new File(MILK_ROOT, "mc_ver");
        File mcJar = new File(new File(MINECRAFT_ROOT, "bin"),
                "minecraft.jar");
        OutputStream out = new FileOutputStream(versionInfo);
        out.write(Util.digest(mcJar));
        out.close();
        
        if(test)
            test();
    }
    
    /**
     * Generates a patched jar for a selection of jarps.
     * @param reqs The jarps to load.
     * @return The patched jar.
     * @throws IOException
     */
    public static File genSelection(Jarp[] reqs) throws IOException{
        File f = File.createTempFile("milk-", ".jar");
        genSelection(reqs, f);
        return f;
    }
    
    /**
     * Generates a patched jar for a selection of jarps.
     * @param reqs The jarps to load.
     * @param out The output location.
     * @throws IOException
     */
    public static void genSelection(Jarp[] reqs, File out)
            throws IOException{
        String[] fullyQualifiedReqs = new String[reqs.length];
        for(int i = 0; i < reqs.length; i++)
            fullyQualifiedReqs[i] = reqs[i].location.getAbsolutePath();
        try{
            JPatch.apply(
                    MINECRAFT_ORIG.getAbsolutePath(),
                    out.getAbsolutePath(),
                    fullyQualifiedReqs);
        }
        catch(Exception e){
            Util.logger.log(Level.SEVERE, "JPatch apply failed.", e);
            throw new IOException(e);
        }
    }
    
    /**
     * Extracts a mod.
     * @param f The mod to extract.
     * @throws IOException
     * @throws MissingReqsException
     */
    public static void extract(File f) throws IOException,
            MissingReqsException{
        File tmpExtracted = milk.jpatch.Util.extractZip(f);
        try{
            Extracter.extract(tmpExtracted, MINECRAFT_ROOT,
                    f.getName().substring(0, f.getName().length() - 4));
        }
        finally{
            milk.jpatch.Util.remDir(tmpExtracted);
        }
    }
    
}
